/*
 *  usb.c : Ethernet-over-USB for BLOB
 *
 *  Copyright (c) 2003, Intel Corporation
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 */

#ifdef HAVE_CONFIG_H
# include <blob/config.h>
#endif

#include <blob/arch.h>
#include <blob/types.h>
#include <blob/serial.h>
#include <blob/util.h>
#include <blob/init.h>

#include <pxa_usb.h>
#include <net.h>

/* USB eth MAC address */
unsigned char eth_mac_addr[6] = {0x0, 0x0, 0x0, 0x0, 0xf, 0xe};

static int usb_rsize=64;
static int usb_wsize=64;
static struct mybuf* cur_tx_buf;
static struct mybuf* next_tx_buf;
static struct mybuf* rx_buf;

#define MIN(a,b) ((a)<(b) ? (a): (b))

#define ETHERNET_VENDOR_ID 0x8086
#define ETHERNET_PRODUCT_ID 0x7d3

static void usb_eth_send_callback(int flag, int size)
{
	/* FIXME: assert cur_tx_buf */

	if( cur_tx_buf ) {
		cur_tx_buf->len = 0;
		cur_tx_buf = 0;
	} 
	else {
		SerialOutputString("Oops: cur_tx_buf is NULL \n");
		return;
	}

	if( next_tx_buf ) {	
		cur_tx_buf = next_tx_buf;
		next_tx_buf = 0;
		
		ep1_send(cur_tx_buf->buf, cur_tx_buf->len, usb_eth_send_callback);
	}
}

static void usb_eth_recv_callback(int flag, int size)
{
	int done=1;

	//SerialOutputString("in usb_eth_recv_callback\n");

	if( flag != 0 ) return;

	/* end-of-packet */
	if(size == usb_rsize) {
		done = 0;
	}

	rx_buf->len += size;

	if( !done ) {
		if(rx_buf->len >= (BUFSZ-usb_rsize)) {
			SerialOutputString("Out of buffer\n");
			return;
		}
		ep2_recv(rx_buf->buf+rx_buf->len, MIN(BUFSZ-rx_buf->len, usb_rsize), usb_eth_recv_callback);
		return;
	}

	/*
	SerialOutputDec(rx_buf->len);
	SerialOutputString(" bytes recved\n");
	*/

	/* let's handle this */
	net_rx();

	/* reset buffer */
	rx_buf->len = 0;
	memset(rx_buf->buf, 0, BUFSZ );
	ep2_recv(rx_buf->buf, usb_rsize, usb_eth_recv_callback);

	return;

}

int eth_xmit(struct mybuf *out) 
{

	if( out->len < usb_wsize ) {
		out->len = usb_wsize + 1;
	} 
	else if (  0 == (out->len % usb_wsize) ) {
		out->len ++;
	}

	if( cur_tx_buf ) {
		if ( next_tx_buf ) {
			SerialOutputString("Droping\n");
			return;
		} else next_tx_buf = out;
	} 
	else {
		cur_tx_buf = out;
		ep1_send(out->buf, out->len , usb_eth_send_callback);
	}
	
	return 0;
}

void usb_driver_reset()
{
	desc_t * pdesc = pxa_usb_get_descriptor_ptr();
	config_desc_t *cfg;
	intf_desc_t *intf;
	ep_desc_t *ep;

	/* setup device descriptor */
	pdesc->dev.idVendor	= ETHERNET_VENDOR_ID;
	pdesc->dev.idProduct    = ETHERNET_PRODUCT_ID;
	pdesc->dev.bNumConfigurations = 1;

	cfg = (config_desc_t*) (pdesc->cdb);

	cfg->bLength             = sizeof( config_desc_t );
	cfg->bDescriptorType     = USB_DESC_CONFIG;
	cfg->wTotalLength        = make_word_c( sizeof(config_desc_t) +
						   sizeof(intf_desc_t) * 1+
						   sizeof(ep_desc_t) * 2);
	cfg->bNumInterfaces      = 1;
	cfg->bConfigurationValue = 1;
	cfg->iConfiguration      = 0;
	cfg->bmAttributes        = USB_CONFIG_BUSPOWERED;
	cfg->MaxPower            = USB_POWER( 500 );

	intf = (intf_desc_t *) ( cfg + 1);
	intf->bLength            = sizeof( intf_desc_t );
	intf->bDescriptorType    = USB_DESC_INTERFACE;
	intf->bInterfaceNumber   = 0; 
	intf->bAlternateSetting  = 0;
	intf->bNumEndpoints      = 2;
	intf->bInterfaceClass    = 0xFF; 
	intf->bInterfaceSubClass = 0;
	intf->bInterfaceProtocol = 0;
	intf->iInterface         = 0;

	ep = (ep_desc_t *) (intf + 1);
	ep[0].bLength             = sizeof( ep_desc_t );
	ep[0].bDescriptorType     = USB_DESC_ENDPOINT;
	ep[0].bEndpointAddress    = USB_EP_ADDRESS( 1, USB_IN );
	ep[0].bmAttributes        = USB_EP_BULK;
	ep[0].wMaxPacketSize      = make_word( 64 );
	ep[0].bInterval           = 0;

	ep[1].bLength             = sizeof( ep_desc_t );
	ep[1].bDescriptorType     = USB_DESC_ENDPOINT;
	ep[1].bEndpointAddress    = USB_EP_ADDRESS( 2, USB_OUT );
	ep[1].bmAttributes        = USB_EP_BULK;
	ep[1].wMaxPacketSize      = make_word( 64 );
	ep[1].bInterval           = 0;

	/* resetting */
	net_reset();

	rx_buf = bget(0);
	rx_buf->len = 0;
	cur_tx_buf = 0;
	next_tx_buf = 0;
	ep2_recv(rx_buf->buf, usb_rsize, usb_eth_recv_callback);
}

int eth_rx()
{
	if ( ICPR & 0x800 ) {
		udc_int_hndlr(0x11, 0);
	}
	return 0;
}
